#!/usr/bin/env python

# Copyright (c) 2007 Johns Hopkins University.
# All rights reserved.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose, without fee, and without written
# agreement is hereby granted, provided that the above copyright
# notice, the (updated) modification history and the author appear in
# all copies of this source code.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS `AS IS'
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS
# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, LOSS OF USE, DATA,
# OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
# THE POSSIBILITY OF SUCH DAMAGE.

# @author Razvan Musaloiu-E. <razvanm@cs.jhu.edu>
# @author Chieh-Jan Mike Liang <cliang4@cs.jhu.edu>

import sys, struct, operator
from xml.dom.minidom import parse

DELUGE_PKTS_PER_PAGE = 48
DELUGE_PKT_PAYLOAD_SIZE = 23
DELUGE_BYTES_PER_PAGE = DELUGE_PKTS_PER_PAGE * DELUGE_PKT_PAYLOAD_SIZE
DELUGE_MAX_PAGES = 128

DELUGE_IDENT_SIZE = 128

def sencode(s, dim):
  s = [ord(c) for c in s]
  if len(s) > dim:
    return s[:dim]
  return s + [0] * (dim - len(s))

# Encode to big endian
def encode(val, dim):
  output = []
  for i in range(dim):
      output.append(val & 0xFF)
      val = val >> 8
  output.reverse()
  return output

def int2byte(v):
    return "".join([struct.pack("B", i) for i in v])

def crc16(data):
  crc = 0
  for b in data:
      crc = crc ^ (b << 8)
      for i in range(0, 8):
          if crc & 0x8000 == 0x8000:
              crc = (crc << 1) ^ 0x1021
          else:
              crc = crc << 1
          crc = crc & 0xffff
  return crc

def pages(data):
  l = len(data) - 2*DELUGE_MAX_PAGES
  if l % DELUGE_BYTES_PER_PAGE != 0:
    sys.stderr.write("ERROR: Bug in padding!")
    sys.exit()
  return l / DELUGE_BYTES_PER_PAGE

def align(data):
  mod = len(data) % DELUGE_BYTES_PER_PAGE
  if mod == 0:
    return data
  return data + [0] * (DELUGE_BYTES_PER_PAGE - mod)

def deluge_ident(data):
  tmp = encode(ident['uidhash'], 4) + \
        encode(ident['size'], 4) + \
        [pages(data), 0]
  crc = crc16(tmp)
  tmp += encode(crc, 2) + \
         sencode(ident['appname'], 16) + \
         sencode(ident['username'], 16) + \
         sencode(ident['hostname'], 16) + \
         sencode(ident['platform'], 16) + \
         encode(ident['timestamp'], 4) + \
         encode(ident['userhash'], 4)
  return tmp + [0] * (DELUGE_IDENT_SIZE - len(tmp))

def deluge_crc(data):
  crc = [0] * DELUGE_MAX_PAGES
  j = 0
  sys.stderr.write("CRCs:\n  ")
  for i in range(0, len(data)-1, DELUGE_BYTES_PER_PAGE):
    crc[j] = crc16(data[i:i+DELUGE_BYTES_PER_PAGE])
    sys.stderr.write("0x%04X " % (crc[j]))
    if (j + 1) % 7 == 0:
      sys.stderr.write("\n  ")
    j += 1
  sys.stderr.write("\n")
  return reduce(operator.add, [encode(i, 2) for i in crc]) + data

for i in range(len(sys.argv)):
  if sys.argv[i] == '-i':
    img_num = int(sys.argv[i+1])
    
dom = parse(sys.argv[-1])
ident = {}
ident_list = [(n.localName, n.firstChild.nodeValue)
              for n in dom.getElementsByTagName('ident')[0].childNodes if n.localName != None]
for (k, v) in ident_list:
  ident[k] = v
for p in ['timestamp', 'userhash', 'uidhash']:
  ident[p] = int(ident[p][:-1], 16)

error = "ERROR: getting the image from the XML file failed."
try:
  image_element = dom.getElementsByTagName('image')[0]
  if image_element.getAttribute('format') != 'ihex':
    error = "ERROR: image format is %s instead of ihex" % image_element.getAttribute('format')
    sys.exit()
  image = image_element.firstChild.nodeValue
except:
  sys.stderr.write(error + '\n')
  sys.exit()

all = []
section = []
end_addr = None
offset = 0
for line in image.split():
    #print "DEBUG:", line
    length = int(line[1:3], 16)
    addr = int(line[3:7], 16) + offset
    rectype = int(line[7:9], 16)
    data = []
    if len(line) > 11:
        data = [int(line[i:i+2], 16) for i in range(9, len(line)-2, 2)]
    crc = int(line[-2:], 16)
    if rectype in [0x00, 0x03]:
        if not end_addr:
            end_addr = addr
            start_addr = addr
        if end_addr != addr:
            all.append((start_addr, section))
            if rectype == 0x03:
                # This last record updates the first 4 bytes which
                # holds some low level configuration. They are the
                # same all the time so I guess that's why they are
                # skipped.
                break
            section = []
            start_addr = addr
        section += data
        end_addr = addr + length
    elif rectype == 0x02:
        offset = int(line[9:9+4], 16) << 4
    elif rectype == 0x01:
        all.append((start_addr, section))
        section = []
        start_addr = addr

sys.stderr.write('Ihex read complete:\n')
sys.stderr.write('  ' + '\n  '.join(["%5d bytes starting at 0x%X" % (len(l), a) for (a, l) in all]))
sys.stderr.write('\n')
sys.stderr.write('  %d bytes in %d sections\n' % (reduce(operator.add, [len(l) for (_, l) in all]), len(all)))

# Usually, there are two sections: one for the code and one for the
# interrupt vector.

all_data = []
for (addr, data) in all:
  all_data += encode(addr, 4) + \
              encode(len(data), 4) + \
              data
all_data += encode(0, 4) + encode(0, 4) # Add the marker for the end of an image
padding = [0] * (DELUGE_BYTES_PER_PAGE - len(all_data) % DELUGE_BYTES_PER_PAGE)
if len(padding) < DELUGE_BYTES_PER_PAGE:
  all_data += padding
all_data = deluge_crc(all_data)
ident['size'] = DELUGE_IDENT_SIZE + len(all_data)
sys.stdout.write(int2byte(deluge_ident(all_data)) + int2byte(all_data))

